package io.github.libkodi.basic_gateway.tools;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.github.libkodi.basic_gateway.context.Context;
import io.vertx.core.Future;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;

/**
 * 类处理方法
 * 给类操作提供需要方法
 */
public class ClassUtils {
	private final static Logger log = LoggerFactory.getLogger(ClassUtils.class);
	
	/**
	 * 扩展包的配置文件
	 */
	private static String EXTEND_CONFIG_FILE = "META-INF/config.json";
	
	/**
	 * 扫描指定包名下的所有类
	 * @param loader 类加载器
	 * @param packname 包名
	 * @return {@link Future<ArrayList<String>>}
	 */
	public static Future<ArrayList<String>> scan(ClassLoader loader, String packname) {
		return Future.future(promise -> {
			String packdir = packname.replace(".", "/");
			ArrayList<String> list = new ArrayList<String>();
			
			try {
				Enumeration<URL> resources = loader.getResources(packdir);
				
				while (resources.hasMoreElements()) {
					URL url = resources.nextElement();
					String protocol = url.getProtocol();
					
					if (protocol.startsWith("file")) {
						scanClassDir(list, packdir, new File(url.getFile()));
					} else if (protocol.startsWith("jar")) {
						scanJarFiles(list, packdir, url);
					}
				}
				
				promise.complete(list);
			} catch (Exception e) {
				promise.fail(e);
			}
		});
	}
	
	/**
	 * 类扫描方法
	 * <p>主Verticle的start方法执行时，自动扫描可识别的类并加载<br/>
	 * <p>该方法加载的类用于后面的路由配置/请求拦截器/配置类等
	 * @param loader 类加载器
	 * @param packname 主Verticle的包名，扫描和分别目标类的依据
	 * @param ctx 
	 * @return {@link Future<ClassFilter>}
	 */
	public static Future<ClassFilter> scaner(ClassLoader loader, String packname, Context ctx) {
		return Future.future(promise -> {
			scan(loader, packname).onSuccess(clazzList -> {
				ClassFilter filter = new ClassFilter(loader, ctx);
				
				try {
					filter.addAll(clazzList);
					
					/**
					 * 扫描其它扩展类
					 */
					Enumeration<URL> extendResources = loader.getResources(EXTEND_CONFIG_FILE);
					
					while (extendResources.hasMoreElements()) {
						URL url = extendResources.nextElement();
						InputStream os = url.openStream(); // 获取配置文件流
						Future<Buffer> contentFuture = StreamUtils.read(os); // 读取配置文件
						
						try {
							os.close();
						} catch (Exception e2) {
							log.error("Error closing InputStream", e2);
						}
						
						if (contentFuture.succeeded()) {
							try {
								JsonObject config = contentFuture.result().toJsonObject();
								
								if (config.containsKey("factories")) {
									JsonArray factories = config.getJsonArray("factories");
									
									if (factories != null) {
										for (int i = 0, len = factories.size(); i < len; i ++) {
											try {
												filter.add(factories.getString(i));
											} catch (Exception e) {
												promise.fail(e);
												return;
											}
										}
									}
								}
							} catch (Exception e) {
								promise.fail("Error parsing configuration file: " + url.getFile());
								return;
							}
						} else {
							promise.fail(contentFuture.cause());
							return;
						}
					}
					
					promise.complete(filter);
				} catch (Throwable e) {
					promise.fail(e);
				}
			}).onFailure(promise::fail);
		});
	}
	
	/**
	 * 从jar文件中扫描目标类
	 * @param classMapper 类管理器
	 * @param packdir 主Verticle的包名
	 * @param url jar文件的URL
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	private static void scanJarFiles(ArrayList<String> clazzList, String packdir, URL url) throws IOException, ClassNotFoundException {
		JarFile jar = ((JarURLConnection)url.openConnection()).getJarFile();
		
		Enumeration<JarEntry> entries = jar.entries();
		
		while (entries.hasMoreElements()) {
			JarEntry file = entries.nextElement();
			String clsName = file.getName();
			
			if (clsName.startsWith(packdir) && clsName.endsWith(".class")) {
				clsName = clsName.replace(".class", "").replace("/", ".");
				
				try {
					clazzList.add(clsName);
				} catch (Exception e) {
					try {
						jar.close();
					} catch (Exception e1) {
						log.error("Error closing file", e1);
					}
					
					throw e;
				}
			}
		}
		
		try {
			jar.close();
		} catch (Exception e) {
			log.error("Error closing jar file", e);
		}
	}
	
	/**
	 * 从目录中扫描目标类
	 * 基本上对开发阶段有效，从本地IDE编译好的class目录中扫描需要的目标类
	 * @param classMapper 类管理器
	 * @param packdir 主Verticle的包名
	 * @param file 目录文件
	 * @throws ClassNotFoundException
	 */
	private static void scanClassDir(ArrayList<String> clazzList, String packdir, File file) throws ClassNotFoundException {
		if (file.exists()) {
			if (file.isDirectory()) {
				File[] list = file.listFiles();
				
				for (File f : list) {
					scanClassDir(clazzList, packdir, f);
				}
			} else {
				String clsName = file.getPath().replaceAll(".*" + packdir, packdir).replace(".class", "").replace("/", ".");
				clazzList.add(clsName);
			}
		}
	}
	
	/**
	 *  创建一个类的实例
	 * @param cls 需要创建实例的类
	 * @param args 参数
	 * @return {@link Object}
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 * @throws SecurityException
	 */
	public static Object newInstance(Class<?> cls, Object... args) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
		return cls.getDeclaredConstructor().newInstance(args);
	}
	
	/**
	 * 给类的实例注入上下文对象
	 * @param instance 类的实例
	 * @param ctx 上下文
	 * @return {@link Future<Void>}
	 */
	public static Future<Void> injectContext(Object instance, Context ctx) {
		return Future.future(promise -> {
			injectField(instance, "ctx", ctx, Context.class).onSuccess(v -> {
				promise.complete();
			}).onFailure(e -> {
				promise.fail(e);
			});
		});
	}

	public static Future<Void> injectField(Object instance, String field, Object value, Class<?> clazz) {
		return Future.future(promise -> {
			Field f = null;
			Class<?> cls = instance.getClass();
			String targetClassName = clazz.getName();
			
			try {
				f = cls.getDeclaredField(field);
				
				if (!f.getType().getName().equals(targetClassName)) {
					f = null;
				}
			} catch (Exception e) {
				cls = cls.getSuperclass();
				
				while (cls != null) {
					try {
						f = cls.getDeclaredField(field);
						
						if (f.getType().getName().equals(targetClassName)) {
							break;
						}
					} catch (Exception e2) {
						cls = cls.getSuperclass();
					}
				}
			}
			
			if (f == null) {
				promise.fail("Missing field: " + instance.getClass().getName() + "." + field);
			} else {
				try {
					f.setAccessible(true);
					f.set(instance, value);
					promise.complete();
				} catch (Exception e) {
					promise.fail(e);
				}
			}
		});
	}
}
